#include <stdio.h>   /* */
#include <stdlib.h>  /* exit() */
#include <string.h>  /* memset(), memcpy() */
#include <sys/utsname.h>   /* uname() */
#include <sys/types.h>
#include <sys/socket.h>   /* socket(), bind(),
                             listen(), accept() */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>  /* fork(), write(), close() */
#include <time.h>
#include <sp.h>
#include <pthread.h>


#include "io.c"

#define BACKLOG 5

#define OPR_READ 1
#define OPR_WRITE 2
#define OPR_END 3

#define NUM_THREADS 2

char *port;
int ret;

mailbox Mbox;
char private_group[MAX_GROUP_NAME];
static char mess[102400];

static int Read_message(){
	char sender[MAX_GROUP_NAME];
	char target_groups[100][MAX_GROUP_NAME];
	int num_groups;
	membership_info memb_info;
	int service_type;
	int16 mess_type;
	int endian_mismatch;
	int i;

	printf("\n================================\n");
	service_type=0;
	ret=SP_receive(Mbox,&service_type,sender,100,&num_groups,target_groups,&mess_type,&endian_mismatch,sizeof(mess),mess);

	if(ret<0){
		SP_error(ret);
		exit(0);
	}
	return ret;
}

static int Send_message(const char* message){
	ret=SP_multicast(Mbox, FIFO_MESS, "mini_matero", 1, strlen(message), message);
	
	if(ret<0){
		SP_error(ret);
		exit(0);
	}
	printf("Mensaje enviado satisfactoriamente!!!!!");
	return ret;
}


void *thread_a_code(void *threadid){

	printf("Thread A\n");

	//logica del thred A
	struct sockaddr_in addr_cliente;
	int len_inet;
	struct addrinfo hints, *res, *p;
	int sockfd, connfd;
	int counter = 0;

	//datos
	int oper = 0;
	char lu[6], nota[3], *resp;
	reg r;
	memset(&r,0,sizeof(reg));
	resp = malloc ( 100 );

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

   	if (getaddrinfo(NULL, port, &hints, &res) != 0){
   		perror("getaddrinfo");
   		exit(1);
   	}

   	for(p = res;p != NULL; p = p->ai_next) {
		void *addr;
		char ipstr[INET_ADDRSTRLEN];

		struct sockaddr_in *an_ipv4_addr = (struct sockaddr_in *)p->ai_addr;
		addr = &(an_ipv4_addr->sin_addr);

		// convert the IP to a string and print it:
		inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
		printf(" %s:%d \n",  ipstr, an_ipv4_addr->sin_port);

		// creo el socket
			if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
				perror("server: socket");
				continue;
			}

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
		    close(sockfd);
		    perror("server: bind");
		    continue;
		}

	}
    
	freeaddrinfo(res); // all done with this structure

	// make it a listening socket. BACKLOG es un advice y cambio desde 2.2
	if (listen(sockfd, BACKLOG) == -1) {
	perror("listen");
	exit(1);
	}

	printf("antes del while\n");
	while(1) {
		printf("Esperando una conexion\n");

		// Wait for a connect:
		len_inet = sizeof addr_cliente;
		connfd = accept(sockfd, (struct sockaddr *)&addr_cliente, (socklen_t*) &len_inet);

		if ( connfd == -1 )
			perror("error Aceptando...");
			
		counter++;
		
		printf("Conección aceptada desde: %s\n", inet_ntoa(addr_cliente.sin_addr));
		
		if(read(connfd, &oper, sizeof(int)) < 0)
			perror("error leyendo el mensaje");
			
		for(; oper != OPR_END; read(connfd, &oper, sizeof(int))) {
			switch(oper) {
				case OPR_READ:
					read(connfd, lu, 5 * sizeof(char));
					lu[5] = '\0';
					printf("LU: %s\n", lu);
					switch(getNota(lu, &r)) {
					case 0:
						sprintf(resp, "%s | %s | ", r.id, r.nombre);
						if(strcmp(r.nota, "-") == 0)
							resp = strcat(resp, "<no_asignada>");
						else
							resp=strcat(resp,r.nota);
						break;
					case -1:
						sprintf(resp, "Alumno con LU%s no existe.", lu);
						break;
					case -2:
						sprintf(resp, "Error al leer la base de datos.");
						break;
					}
					strcat(resp, "\0");
					printf("Respuesta: %s.\n", resp);
					write(connfd,resp,strlen(resp)*sizeof(char));
					break;
					
				case OPR_WRITE:
					read(connfd, lu, 5 * sizeof(char));
					lu[5] = '\0';
					printf("LU: %s\n", lu);
					read(connfd, nota, sizeof(nota));
					printf("Nota: %s\n", nota);
					switch(getNota(lu, &r)) {
					case 0:
						if(strcmp(r.nota, "-") == 0) {
							setNota(lu, nota);
							sprintf(resp, "%s;%s;%s", r.id, r.nombre, nota);

							// mandar mensaje SPREAD
							Send_message(resp);

						} else
							sprintf(resp, "Alumno con LU%s ya tiene nota asignada.", lu);
						break;
					case -1:
						sprintf(resp, "Alumno con LU%s no existe.", lu);
						break;
					case -2:
						sprintf(resp, "Error al leer la base de datos.");
						break;
					}
					strcat(resp, "\0");
					printf("Respuesta: %s.\n", resp);
					write(connfd,resp,sizeof(resp));

					break;
			}
		}
		printf("Cerrando coneccion con: %s\n", inet_ntoa(addr_cliente.sin_addr));
		close(connfd); // cerrar socket
	} // end while

	close(sockfd);
	return 0;
}

void *thread_b_code(void *threadid){

	printf("Thread B\n");

	//logica del thred B
	reg r;
	char lu[6], nota[3],*resp;
	memset(&r,0,sizeof(reg));
	resp = malloc ( 100 );

	while(1) {
		//recibir mensaje spread
		Read_message();

		deserializeLine(mess, &r);

		printf("LU: %s\n", r.id);
		printf("Nota: %s\n", r.nota);
		setNota(r.id, r.nota);
		/*
		switch(getNota(lu, &r)) {
		case 0:
			setNota(lu, nota);
			sprintf(resp, "%s | %s | %s", r.id, r.nombre, nota);
			break;
		case -1:
			sprintf(resp, "Alumno con LU%s no existe.", lu);
			break;
		case -2:
			sprintf(resp, "Error al leer la base de datos.");
			break;
		}
		strcat(resp, "\0");
		printf("Respuesta: %s.\n", resp);
*/
	}
}
	
int main(int argc,char **argv) {

	printf("No ha ingresado el numero de puerto\n");
	pthread_t thread_a;
	pthread_t thread_b;
	
	long t;

	int rtn_a;
	int rtn_b;

	//informacion de la direccion del servidor
	if(argc!=2){
		printf("No ha ingresado el numero de puerto\n");
		return 1;
	}

	port=argv[1];

	
	ret=SP_connect("3333@localhost",NULL,0,0,&Mbox,private_group);
	if(ret<0){
	    printf("ERROR: ");
	    SP_error(ret);
	    exit(0);	
	}
	else 
		printf("Conectado! Grupo privado asignado: %s\n",private_group);

	ret=SP_join(Mbox,"mini_matero");
	
	if(ret<0){
	    printf("ERROR: ");
	    SP_error(ret);
	    exit(0);	
	}
	else printf("Exito!!!");
	

	printf("Iniciado los thread\n");
	rtn_a = pthread_create(&thread_a, NULL, thread_a_code, (void *)t);
	rtn_b = pthread_create(&thread_b, NULL, thread_b_code, (void *)t);
	printf("threads inicializados\n");

	while(1) {;}
/*
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;


		struct sockaddr_in addr_cliente;
		int len_inet;
		struct addrinfo hints, *res, *p;
		int sockfd, connfd;
		int counter = 0;

		//datos
		int oper = 0;
		char lu[6], nota[3], *resp;
		reg r;
		memset(&r,0,sizeof(reg));
		resp = malloc ( 100 );

		memset(&hints, 0, sizeof hints);
		hints.ai_family = AF_INET;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_PASSIVE;

  
		if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
			perror("server: socket");
			continue;
		}

		for(p = res;p != NULL; p = p->ai_next) {
			void *addr;
			char ipstr[INET_ADDRSTRLEN];

			struct sockaddr_in *an_ipv4_addr = (struct sockaddr_in *)p->ai_addr;
			addr = &(an_ipv4_addr->sin_addr);

    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }


			if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
				perror("server: socket");
				continue;
			}

	while(1) {
		len_inet = sizeof addr_cliente;
		connfd = accept(sockfd, (struct sockaddr *)&addr_cliente, (socklen_t*) &len_inet);

		if (listen(sockfd, BACKLOG) == -1) {
			perror("listen");
			exit(1);
		}


		while(1) {
			len_inet = sizeof addr_cliente;
			connfd = accept(sockfd, (struct sockaddr *)&addr_cliente, (socklen_t*) &len_inet);

			if ( connfd == -1 )
				perror("error Aceptando...");

			counter++;

			printf("Conección aceptada desde: %s\n", inet_ntoa(addr_cliente.sin_addr));

			if(read(connfd, &oper, sizeof(int)) < 0)
				perror("error leyendo el mensaje");

			for(; oper != OPR_END; read(connfd, &oper, sizeof(int))) {
				switch(oper) {
					case OPR_READ:
						read(connfd, lu, 5 * sizeof(char));
						lu[5] = '\0';
						printf("LU: %s\n", lu);
						switch(getNota(lu, &r)) {
						case 0:
							sprintf(resp, "%s | %s | ", r.id, r.nombre);
							if(strcmp(r.nota, "-") == 0)
								resp = strcat(resp, "<no_asignada>");
							else
								resp=strcat(resp,r.nota);
							break;
						case -1:
							sprintf(resp, "Alumno con LU%s no existe.", lu);
							break;
						case -2:
							sprintf(resp, "Error al leer la base de datos.");
							break;
						}
						strcat(resp, "\0");
						printf("Respuesta: %s.\n", resp);
						write(connfd,resp,strlen(resp)*sizeof(char));
						break;
		
					case OPR_WRITE:
						read(connfd, lu, 5 * sizeof(char));
						lu[5] = '\0';
						printf("LU: %s\n", lu);
						read(connfd, nota, sizeof(nota));
						printf("Nota: %s\n", nota);
						switch(getNota(lu, &r)) {
						case 0:
							if(strcmp(r.nota, "-") == 0) {
								setNota(lu, nota);
								sprintf(resp, "%s | %s | %s", r.id, r.nombre, nota);
							} else
								sprintf(resp, "Alumno con LU%s ya tiene nota asignada.", lu);
							break;
						case -1:
							sprintf(resp, "Alumno con LU%s no existe.", lu);
							break;
						case -2:
							sprintf(resp, "Error al leer la base de datos.");
							break;
						}
						strcat(resp, "\0");
						printf("Respuesta: %s.\n", resp);
						write(connfd,resp,sizeof(resp));
						break;
					case -1:
						sprintf(resp, "Alumno con LU%s no existe.", lu);
						break;
					case -2:
						sprintf(resp, "Error al leer la base de datos.");
						break;
					}
					strcat(resp, "\0");
					printf("Respuesta: %s.\n", resp);
					write(connfd,resp,strlen(resp)*sizeof(char));
					break;
					
				case OPR_WRITE:
					ret=Read_message();					
					read(connfd, lu, 5 * sizeof(char));
					lu[5] = '\0';
					printf("LU: %s\n", lu);
					read(connfd, nota, sizeof(nota));
					printf("Nota: %s\n", nota);
					switch(getNota(lu, &r)) {
					case 0:
						if(strcmp(r.nota, "-") == 0) {
							setNota(lu, nota);
							sprintf(resp, "%s | %s | %s", r.id, r.nombre, nota);
						} else
							sprintf(resp, "Alumno con LU%s ya tiene nota asignada.", lu);
						break;
					case -1:
						sprintf(resp, "Alumno con LU%s no existe.", lu);
						break;
					case -2:
						sprintf(resp, "Error al leer la base de datos.");
						break;
					}
					strcat(resp, "\0");
					printf("Respuesta: %s.\n", resp);
					write(connfd,resp,sizeof(resp));
					break;
			}
		}
		printf("Cerrando coneccion con: %s\n", inet_ntoa(addr_cliente.sin_addr));
		close(connfd);
	} 

	close(sockfd); */
	return 0;
}
